package body MIDI.XMIDI is

   procedure Open (this : in out XMI_File_Type; Filename : String)
   is
   begin
      Stream_IO.Open(this.File, Name => Filename, Mode => Stream_IO.In_File);
      this.As_Stream := Stream_IO.Stream(this.File);
   end Open;

   function Read_Text (
      this : in out XMI_File_Type;
      Size : Uint32_be
      ) return String
   is
      Result : String(1 .. Natural(Size));
   begin
      String'Read(this.As_Stream, Result);
      return Result;
   end Read_Text;

   procedure Visit (
      this : in out XMI_File_Type;
      On_INFO, On_TIMB, On_RBRN, On_EVNT : access function return Boolean := null;
      On_Trunk_Begin, On_Trunk_End : access procedure (T : Trunk_Head_Type) := null;
      On_Path_Begin, On_Path_End : access procedure (Name : Trunk_Name_Type) := null
      )
   is
      use type Stream_IO.Count;
      Size     : Stream_IO.Count := Stream_IO.Size(this.File);
      Continue : Boolean := True;

      procedure Read_Trunk;

      procedure Read_Path (Path : Trunk_Head_Type);

      procedure Read_Trunk
      is
         use type Stream_IO.Count;
         T : Trunk_Head_Type := Trunk_Head_Type'Input(this.As_Stream);
      begin
         if On_Trunk_Begin /= null then
            On_Trunk_Begin(T);
         end if;
         if T.Name in "FORM" | "CAT " then
            Read_Path(T);
         elsif T.Name = "INFO" then
            if On_INFO = null then
               this.Next(T.Size);
            else
               Continue := On_INFO.all;
            end if;
         elsif T.Name = "TIMB" then
            if On_TIMB = null then
               this.Next(T.Size);
            else
               Continue := On_TIMB.all;
            end if;
         elsif T.Name = "RBRN" then
            if On_RBRN = null then
               this.Next(T.Size);
            else
               Continue := On_RBRN.all;
            end if;
         elsif T.Name = "EVNT" then
            if On_EVNT = null then
               this.Next(T.Size);
            else
               Continue := On_EVNT.all;
            end if;
         else
            this.Next(T.Size);
         end if;
         if On_Trunk_End /= null then
            On_Trunk_End(T);
         end if;
      end Read_Trunk;

      procedure Read_Path (Path : Trunk_Head_Type)
      is
         use type Stream_IO.Count;
         K : Trunk_Name_Type;
      begin
         String'Read(this.As_Stream, K);
         if On_Path_Begin /= null then
            On_Path_Begin(K);
         end if;
         declare
            F : String := Path.Name & K;
         begin
            if F in "FORMXDIR" | "CAT XMID" then
               Read_Trunk;
            elsif F = "FORMXMID" then
               while not this.End_Of_File loop
                  Read_Trunk;
               end loop;
            else
               this.Next(Path.Size - K'Length);
            end if;
         end;
         if On_Path_End /= null then
            On_Path_End(K);
         end if;
      end Read_Path;
   begin
      while not Stream_IO.End_Of_File(this.File) loop
         Read_Trunk;
         exit when not Continue;
      end loop;
   end Visit;

   function End_Of_File (this : XMI_File_Type) return Boolean
   is
   begin
      return Stream_IO.End_Of_File(this.File);
   end End_Of_File;

   procedure Next (
      this : in out XMI_File_Type;
      Size : Uint32_be
      )
   is
      use Stream_IO;
   begin
      Set_Index(this.File, Index(this.File) + Count(Size));
   end Next;

   procedure Close (this : in out XMI_File_Type)
   is
   begin
      Stream_IO.Close(this.File);
   end Close;

end MIDI.XMIDI;
